package com.cabinet;

import com.cabinet.db.DBConferenceCfg;
import com.cabinet.db.DBDeviceState;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;

import java.io.*;
import java.util.*;

/**
* Created by IntelliJ IDEA.
* User: denny
* Date: 8/20/13
* Time: 5:45 PM
* To change this template use File | Settings | File Templates.
*/
public class SendReceiveThread extends Thread{
  public static final SendReceiveThread INST = new SendReceiveThread();

  volatile OutPacket outPacket = new OutPacket();
  volatile InPacket lastInPacket = null;
  List<PortChangeListener> portChangeListeners = new ArrayList<>();

  private SendReceiveThread() {
    super("SendReceiveThread");
    setPriority(Thread.MAX_PRIORITY);
  }

  public void addPortChangeListener(PortChangeListener l){
    portChangeListeners.add(l);
  }

  boolean hasCall(){
    InPacket p = lastInPacket;
    if(p==null){
      return false;
    }
    return p.hasCall();
  }

  public void setConferenceConfig(DBConferenceCfg conf){
    OutPacket out = new OutPacket();

    /*
    outPacket.data[0] = (byte)0x80;
    outPacket.data[1] = (byte)0xf0;
    outPacket.data[2] = 3;
    */
//    outPacket.setWorkConference(2, 5, true, true);
//    outPacket.setWorkConference(3, 5, true, true);
//    outPacket.setWorkConference(Device.DEVICE1, 5, false, true);
//    outPacket.setWorkConference(Device.TEACHER, 5, true, true);

    if( conf!=null ){
      for( DBDeviceState dev : conf.getDevStates() ){
        setOut(out, dev);
        //System.out.println("devid="+dev.getDevId()+" conf="+dev.getConferenceId());
      }
      if( hasCall() ){
        out.setBit(Device.TEACHER, 7, true);
      }
    }

    System.out.println("SendReceiveThread.setConferenceConfig()");
    out.logOutData(outPacket);
    outPacket = out;
    notifyPortChangeListeners(outPacket, null);
  }

  private void setOut(OutPacket out, DBDeviceState dev) {
    if( dev.getConferenceId()>=0 && dev.getConferenceId()<=7 ){
      out.setWorkConference((int)dev.getDevId(), (int)dev.getConferenceId(),
         dev.isListen(), dev.isSpeak() );
    }
    CodesBlockName block=null;
    if( dev.getConferenceId()==DBDeviceState.TEACHERS_CONFERENCE ){
      if( dev.isListen() && dev.isSpeak() ){
        block = CodesBlockName.BLOCK_teacher_conf_IN_OUT;
      } else if ( dev.isSpeak() ){
        block = CodesBlockName.BLOCK_teacher_conf_OUT;
      } else if ( dev.isListen() ){
        block = CodesBlockName.BLOCK_teacher_conf_IN;
      }
    }
    if( dev.getConferenceId()==DBDeviceState.OFF_CONFERENCE ){
      block = CodesBlockName.BLOCK_off;
    }
    if( dev.isSelfTest() ){
      if( dev.isSpeak() ){
        block = CodesBlockName.BLOCK_self_test_OUT;
      }else{
        block = CodesBlockName.BLOCK_self_test;
      }
    }
    if( block!=null ){
      CodesBlock codesBlock= Config.codesBlocks.get(block.name());;
      if( codesBlock==null ){
        throw new IllegalStateException("no codes block "+block);
      }
      out.data[(int)dev.getDevId()] = codesBlock.list.get((int)dev.getDevId());
    }
  }

  public static String binaryVal(byte b){
    char[] c = new char[8];
    for(int i=0; i<8; i++ ){
      if( 0 != (b & (1<<(7-i))) ){
        c[i] = '1';
      }else{
        c[i] = '0';
      }
    }
    return new String(c);
  }

  public static class ClassScope {
    private static java.lang.reflect.Field LIBRARIES=null;
    static {
      try {
        LIBRARIES = ClassLoader.class.getDeclaredField("loadedLibraryNames");
      } catch (NoSuchFieldException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
      LIBRARIES.setAccessible(true);
    }
    public static String[] getLoadedLibraries(final ClassLoader loader) {
      final Vector<String> libraries;
      try {
        libraries = (Vector<String>) LIBRARIES.get(loader);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      }
      return libraries.toArray(new String[] {});
    }
  }


  @Override
  public void run() {
    System.out.println( "java.library.path=" + System.getProperty("java.library.path") );
    Enumeration en = gnu.io.CommPortIdentifier.getPortIdentifiers();
    while(en.hasMoreElements()){
      CommPortIdentifier comPort = (CommPortIdentifier)en.nextElement();
      System.out.println("found com port "+ comPort.getName());
    }

    final String[] libraries = ClassScope.getLoadedLibraries(ClassLoader.getSystemClassLoader()); //MyClassName.class.getClassLoader()
    System.out.println("native libraries: " + Arrays.asList(libraries));

    //connect("COM3");
    //connect("/dev/ttyUSB0");
    try {
      connect(Config.str("COM-port"));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  void connect ( String portName ) throws Exception
  {
    CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
    if ( portIdentifier.isCurrentlyOwned() )
    {
      System.out.println("Error: Port is currently in use");
    }
    else
    {
      CommPort commPort = portIdentifier.open("LINGAFON1514",10_000);

      if ( commPort instanceof SerialPort)
      {
        SerialPort serialPort = (SerialPort) commPort;
        //int speed = 115200;
        int speed = Config.getInt("COM-port-speed");

        serialPort.setSerialPortParams(speed,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);
        serialPort.enableReceiveTimeout(5000 /*millis*/);

        InputStream in = serialPort.getInputStream();
        DataInputStream dataInput = new DataInputStream(in);
        OutputStream out = serialPort.getOutputStream();

        System.out.println(portName + " Connect ok");

        OutPacket trackPrevOutPacket = null;


        for(;;){
          if( trackPrevOutPacket != outPacket ){
            trackPrevOutPacket = outPacket;
            System.out.println("New config for COM port:");
            trackPrevOutPacket.logOutData(null);
          }

          out.write(trackPrevOutPacket.data);
          out.flush();
          System.out.println();
          System.out.println("reading reply...");
          InPacket inPacket = new InPacket();
          dataInput.readFully(inPacket.data);
          inPacket.logInData();
          lastInPacket = inPacket;

          notifyPortChangeListeners(trackPrevOutPacket, inPacket);

          Thread.sleep(250);
        }

        /*
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String line;
        while((line=br.readLine())!=null){
          System.out.println("from serial port: "+line);
        }
        */

//        (new Thread(new SerialReader(in))).start();
//        (new Thread(new SerialWriter(out))).start();

      }
      else
      {
        System.out.println("Error: Only serial ports are handled by this example.");
      }
    }
  }

  private void notifyPortChangeListeners(OutPacket trackPrevOutPacket, InPacket inPacket) {
    for( PortChangeListener l : portChangeListeners ){
      l.change(trackPrevOutPacket.data, inPacket==null ? null : inPacket.data);
    }
  }

}
